#include "Race.h"

#include "lib.h"

#include "ATL.h"

#include <boost/assign/list_of.hpp>

using namespace std;
using namespace boost::assign;

bool Race::NiveauxLus( false );

const vector< string > Race::NomsNiveaux = 
  list_of( "Race::elementaire" )( "Race::fonction" );

const unsigned Race::PasTrouve( 0xFFFF );

DebugLevelType Race::elementaire  = BLANK;
DebugLevelType Race::fonction     = BLANK;

/**
 * Constructeur standard. Est utilisé lors de la création d'une Race au
 * travers de l'interface graphique et du mode console. Initialise les
 * pointeurs à zéro et le nom de la Race.
 *
 * @param[in] type nom de la race
 */
Race::Race(const string& type)
  : ADDObjetBase( type ), Type( type ), NombrePPsAlloues( 0 ),
    NombreTotalCapacites( 0 ), NombreTotalEnsembles( 0 ),
    NombreMaxCapacites( 0 )
{
  BonusAuTrait = 0;

  if ( ! NiveauxLus )
    {
      lireNiveaux( this, NomsNiveaux);
      NiveauxLus = true;
    }
}

/**
 * Constructeur de copie, réalise une copie profonde. Est utilisé lors
 * de la création d'un personnage (à partir d'une Race et d'une
 * Classe). Les capacités, les pointeurs sont copiés.
 *
 * @param[in] race race à copier
 */
Race::Race(const Race& race)
  : ADDObjetBase( race.Type ), Type( race.Type ),
    NombrePPsAlloues( race.NombrePPsAlloues ),
    NombreTotalCapacites( race.NombreTotalCapacites ),
    NombreTotalEnsembles( race.NombreTotalEnsembles ),
    NombreMaxCapacites( race.NombreMaxCapacites )
{
  CapacitesRaciales.assign( race.CapacitesRaciales.begin(),
			    race.CapacitesRaciales.end() );
  EnsemblesRaciaux.assign( race.EnsemblesRaciaux.begin(),
			   race.EnsemblesRaciaux.end() );
  PrerequisRaciaux.assign( race.PrerequisRaciaux.begin(),
			   race.PrerequisRaciaux.end() );

  Capacites.assign( race.Capacites.begin(), race.Capacites.end() );
  BonusAuTrait = race.BonusAuTrait;

  AjustementsRaciaux = race.AjustementsRaciaux;

  if ( ! NiveauxLus )
    {
      lireNiveaux( this, NomsNiveaux);
      NiveauxLus = true;
    }
}

/**
 * Le destructeur doit libérer la mémoire allouée pour le modificateur
 * de trait au besoin.
 */
Race::~Race()
{
  if ( BonusAuTrait != 0 )
    delete BonusAuTrait;
}

/**
 * Permet de créer une Race quelconque en sélectionnant les
 * capacités. Fonctionne comme les anciens constructeurs des classes
 * dérivées.
 *
 * @param[in] pps nombre de points de personnages (PP) à disposition
 * pour la construction, une fois la Race construite, l'argument
 * contient le nombre de PP restant pour la suite de la création de
 * personnage.
 */
void Race::construitRace(unsigned& pps)
{
  /*Debug::*/empileNiveau( &elementaire );
  func() << "Race::construitRace -> début" << endmsg;

  unsigned i, choix( NombreTotalEnsembles + 1 ), confirmation, savePP( pps ),
    nbrActuel;

  if ( EnsemblesRaciaux.size() > 0 )
    {
      do
	{
	  pps = savePP;
	  blank() << "Sélectionner l'ensembla racial souhaité :" << endmsg;
	  for (i = 0; i < NombreTotalEnsembles; i++)
	    blank() << i + 1 <<' ' << EnsemblesRaciaux.at( i ) . nom()
		    << " (pour " << EnsemblesRaciaux.at( i ) . prix() << " pp)"
		    << endmsg;
	  blank() << NombreTotalEnsembles + 1 << " Aucun" << endmsg;

	  choix = demanderUnsignedBorne( "(" + ecrireString( pps ) +
					 "pp) Sélection : ",
					 1, NombreTotalEnsembles + 1 );

	  if ( choix != NombreTotalEnsembles + 1 )
	    {
	      if ( pps >= EnsemblesRaciaux.at( choix - 1 ) . prix() )
		{
		  blank() << "Ensemble sélectionné : "
			  << EnsemblesRaciaux.at( choix - 1 ) . nom() << endmsg;
		  pps -= EnsemblesRaciaux.at( choix - 1 ) . prix();
		}
	      else
		{
		  warning() << "Pas assez de pp" << endmsg;
		  continue;
		}
	    }
	  else
	    blank() << "Aucun ensemble sélectionné" << endmsg;

	  blank() << "Entrer 1 pour continuer, 2 pour annuler" << endmsg;
	  confirmation = demanderUnsignedBorne( "Choix : ", 1, 2 );
	}
      while ( confirmation == 2 );
    }

  savePP = pps;

  if ( choix == NombreTotalEnsembles + 1 )
    {
      // Il faut afficher toutes les compétences et faire une sélection.

      blank() << "Sélectionner les capacités raciales :" << endmsg;
      for (i = 0; i < NombreTotalCapacites; i++)
	blank() << i + 1 << ' ' << CapacitesRaciales.at( i ) .nom()
		<< " (pour " << CapacitesRaciales.at( i ) . prix() << ")" << endmsg;
      blank() << NombreTotalCapacites + 1 << " Aucune" << endmsg;

      do
	{
	  Capacites.clear();
	  pps = savePP;
	  do
	    {
	      choix = demanderUnsignedBorne( "(" + ecrireString( pps ) +
					     "pp) Sélection : ",
					     1, NombreTotalCapacites + 1 );
	      
	      if ( choix != NombreTotalCapacites + 1 )
		{
		  if ( pps >= CapacitesRaciales.at( choix - 1 ) . prix() )
		    {
		      if ( safePushBack( &Capacites, choix - 1 ) )
			warning() << "Compétence déjà prise" << endmsg;
		      else
			{
			  blank() << "Capacité sélectionnée : "
				  << CapacitesRaciales.at( choix - 1 ) .nom()
				  << endmsg;
			  pps -= CapacitesRaciales.at( choix - 1 ) . prix();
			  Capacites.push_back( choix - 1 );
			}
		    }
		  else
		    {
		      warning() << "Pas assez de pp" << endmsg;
		      continue;
		    }
		}
	    }
	  while ( choix != NombreTotalCapacites + 1 );

	  nbrActuel = Capacites.size();

	  if ( nbrActuel > 0 )
	    {
	      blank() << "Récapitulatif des capacités: " << endmsg;
	      afficheInfo( blank().stream() );
	      blank() << endmsg;
	    }
	  else
	    blank() << "Aucune capacité sélectionnée" << endmsg;

	  blank() << "Entrer 1 pour continuer, 2 pour annuler" << endmsg;
	  confirmation = demanderUnsignedBorne( "Choix : ", 1, 2);
	}
      while ( confirmation == 2 );
    }
  else
    {
      // On a sélectionné un ensemble
      Capacites.resize( EnsemblesRaciaux[ choix - 1 ] . membres() . size() );
      transform( EnsemblesRaciaux[ choix - 1 ] . membres() . begin(),
		 EnsemblesRaciaux[ choix - 1 ] . membres() . end(),
		 Capacites.begin(), bind2nd( minus< unsigned >(), 1 ) );
    }

  func() << "Race::construitRace -> fin" << endmsg;
  /*Debug::*/depileNiveau();
}

/**
 * Retourne le nombre de capacités raciales possédées par le
 * personnage.
 *
 * @return la dimension du conteneur Capacites.
 */
unsigned Race::tailleCapacites() const
{
  return Capacites.size();
}

/**
 * Retourne la capacité désirée. Si l'on demande une capacité
 * inexistante, par exemple en demandant la capacité numéro 10 et que
 * le nombre de capacités est 5, un message d'erreur est affiché et on
 * retourne PasTrouve.
 *
 * Lors d'un accès au nom d'une capacité ou à une capacité (CapaciteRaciale),
 * on comment toujours par un appel à numCapacité.
 *
 * Le niveau de sortie est paramétré par Race::élémentaire :\n
 * \e FUNC permet d'afficher le nom de la méthode en début et fin
 * d'exécution\n
 *
 * @param[in] i la position de la capacité dans la liste.
 *
 * @return une référence constante sur le numéro de la capacité.
 */
const unsigned & Race::numCapacite(const unsigned& i) const
{
  /*Debug::*/empileNiveau( &elementaire );
  func() << "Race::capa -> début" << endmsg;

  if (i < Capacites.size())
    {
      func() << "Race::capa -> fin" << endmsg;
      /*Debug::*/depileNiveau();
      return Capacites[ i ];
    }
  else
    {
      error() << "Compétence inexistante, dépassement de portée" << endmsg;
      func() << "Race::capa -> fin" << endmsg;
      /*Debug::*/depileNiveau();
      return PasTrouve;
    }
}

/**
 * Permet de récupérer le nombre de PP disponible lors de la création
 * d'une Race.
 *
 * @return nombre de PP à disposition.
 *
 * @todo Il me semble que le nombre de PP à disposition existe dans le
 * XML, il faudrait utiliser ce champ et renvoyer une const ref. Si
 * n'existe pas dans le XML, alors ajouter et faire les modifications
 * nécessaires.
 */
const unsigned& Race::pp() const
{
  return NombrePPsAlloues;
}

/**
 * Retourne la race (càd le nom).
 *
 * @return le nom de la race
 */
const string& Race::type() const
{
  return Type;
}

/**
 * Retrouve la capacité raciale désirée. Fonctionne sur le même
 * principe que numCapacite(), mais renvoie le nom de la capacité au lieu de
 * son numéro. Un message d'erreur est affiché si la capacité est
 * inexistante, et on retourne une string vide.\n
 *
 * On obient une instance de CapaciteRaciale grâce à la méthode
 * capaciteNumero, et on retourne la valeur du champ Nom.
 *
 * Le niveau de sortie est paramétré par Race::élémentaire :\n
 * \e FUNC permet d'afficher le nom de la méthode en début et fin
 * d'exécution
 *
 * @param[in] i numéro de la capacité.
 *
 * @return une référence constante sur le nom de la capacité.
 */
const string& Race::nomCapacite(const unsigned& i) const
{
  /*Debug::*/empileNiveau( &elementaire );
  func() << "Race::nomCapacite -> début" << endmsg;

  const CapaciteRaciale * ptr = &( capaciteNumero( i ) );
  
  func() << "Race::nomCapacite -> fin" << endmsg;
  /*Debug::*/depileNiveau();
  return ptr -> nom();
}

/**
 * Permet de récupérer l'instance de CapaciteRaciale donnée par la position
 * dans le conteneur des capacités raciales de l'instance courante.
 *
 * Le numéro de la capacité est récupéré par la méthode numCapacite.
 *
 * @param[in] i indice de la capacité dans Capacites.
 *
 * @return une référence constante sur la CapaciteRaciale voulue, ou sur
 * CapaciteRacialeInconnue si la capacité voulue n'existe pas.
 */
const CapaciteRaciale& Race::capaciteNumero( const unsigned & i ) const
{
  /*Debug::*/empileNiveau( &elementaire );
  func() << "Race::capaciteNumero -> début" << endmsg;

  unsigned numero( numCapacite( i ) );

  if ( numero == 0xFFFF )
    {
      warning() << "Tentative d'accéder à une capacité inconnue" << endmsg;
      func() << "Race::capaciteNumero -> fin" << endmsg;
      /*Debug::*/depileNiveau();
      return capaciteRacialeInconnue();
    }

  func() << "Race::capaciteNumero -> fin" << endmsg;
  /*Debug::*/depileNiveau();
  return CapacitesRaciales[ numero ];
}

const unsigned& Race::nombrePPsAlloues() const
{
  return NombrePPsAlloues;
}

/**
 * Permet de récupérer un pointeur sur les capacités que l'instance de
 * Race possède.
 *
 * @return l'adressed de Capacites.
 */
const vector< unsigned >* Race::adresseCapacites() const
{
  return &Capacites;
}

/**
 * Permet de récupérer un pointeur sur les capacités que l'instance de
 * Race possède, ainsi que leur nombre.
 *
 * @param[out] nbr nombre de capacités.
 * 
 * @return l'adressed de Capacites.
 */
const vector<unsigned>* Race::capacites(unsigned& nbr) const
{
  nbr = Capacites.size();
  
  return &Capacites;
}

/**
 * Réalise une copie de toutes les capacités de la Race
 * considérée. Est utilisée lors de la création de la Race, en mode
 * console ou graphique, sert également lors de l'écriture des
 * feuilles de perso.
 *
 * Le niveau de sortie est paramétré par Race::elementaire :\n
 * \e FUNC permet d'afficher le nom de la méthode en début et fin
 * d'exécution
 *
 * @param[out] nbr contient le nombre de capacités
 * @param[out] capa contient les capacités
 */
void Race::copieCapacitesRaciales(unsigned& nbr, vector<CapaciteRaciale>& capa) const
{
  /*Debug::*/empileNiveau( &elementaire );
  func() << "Race::copieCapacitesRaciales -> début" << endmsg;
  
  nbr = NombreTotalCapacites;

  capa.assign( CapacitesRaciales.begin(), CapacitesRaciales.end() );

  func() << "Race::copieCapacitesRaciales -> fin" << endmsg;
  /*Debug::*/depileNiveau();
}

/**
 * Permet de récupérer un pointeur sur l'ensemble des capacités
 * raciales.
 *
 * @return l'adresse de CapacitesRaciales.
 */
const vector< CapaciteRaciale >* Race::capacitesRaciales() const
{
  return &CapacitesRaciales;
}

/**
 * Permet de récupérer le nombre d'ensembles raciaux, ainsi que le
 * nombre maximal de compétences dans un ensemble.
 *
 * @param[out] nbr nombre d'ensembles (écrit par la méthode).
 * @param[out] max nombre maximal de compétences dans un ensemble (écrit
 * par la méthode).
 */
void Race::donneesEnsRaciaux(unsigned& nbr, unsigned& max) const
{
  nbr = NombreTotalEnsembles;
  max = NombreMaxCapacites;
}

/**
 * Permet de récupérer un pointeur sur normalement le premier ensemble
 * racial.
 *
 * @return pointeur sur le conteneur des noms des ensembles raciaux.
 */
const vector< EnsembleCapacitesRaciales >* Race::ensemblesRaciaux() const
{
  return &EnsemblesRaciaux;
}

void Race::copiePrerequisRaciaux( vector< Prerequis >& prerequis ) const
{
  prerequis.assign( PrerequisRaciaux.begin(), PrerequisRaciaux.end() );
}

void Race::copieAjustementsRaciaux( map< unsigned, int >& ajustements ) const
{
  ajustements = AjustementsRaciaux;
}

/**
 * Copie les capacités raciales dans le flot passé en argument. Par
 * défaut on réalise ici un affichage sur la sortie standard. Mais on
 * peut également récupérer les informations pour faire un affichage
 * dans une fenêtre.
 *
 * Fait appel à nomCapacite().
 *
 * Le niveau de sortie est paramétré par Race::fonction :\n
 * \e FUNC permet d'afficher le nom de la méthode en début et fin
 * d'exécution\n
 * \e LFUNC permet d'afficher de numéro de chaque capacité\n
 * \e DEBUG permet d'afficher le nombre de capacités\n
 *
 * @param[in] os flot, par défaut blank() est utilisé
 */
void Race::afficheInfo(ostream& os) const
{
  /*Debug::*/empileNiveau( &fonction );
  func() << "Race::afficheInfo -> début" << endmsg;

  os << "Race : " << Type << endmsg;

  unsigned nbr( tailleCapacites() );

  debug() << "Nbr de capacités " << nbr << endmsg;

  for (unsigned i( 0 ); i < nbr; i++)
    {
      lfunc() << i << "-ème capacité " << numCapacite( i ) << endmsg;
      os << nomCapacite( i ) << endmsg;
    }

  func() << "Race::afficheInfo -> fin" << endmsg;
  /*Debug::*/depileNiveau();
}

/**
 * Copie les capacités raciales dans le flot passé en argument. Par
 * défaut on réalise ici un affichage sur la sortie standard. Mais on
 * peut également récupérer les informations pour faire un affichage
 * dans une fenêtre.
 *
 * Fait appel à nomCapacite().
 *
 * Le niveau de sortie est paramétré par Race::fonction :\n
 * \e FUNC permet d'afficher le nom de la méthode en début et fin
 * d'exécution\n
 * \e LFUNC permet d'afficher de numéro de chaque capacité\n
 * \e DEBUG permet d'afficher le nombre de capacités\n
 *
 * @param[in] os flot, par défaut blank() est utilisé
 */
void Race::afficheInfo( FlotMessage& flot ) const
{
  /*Debug::*/empileNiveau( &fonction );
  func() << "Race::afficheInfo -> début" << endmsg;

  flot << "Race : " << Type << endmsg;

  unsigned nbr( tailleCapacites() );

  debug() << "Nbr de capacités " << nbr << endmsg;

  for (unsigned i( 0 ); i < nbr; i++)
    {
      lfunc() << i << "-ème capacité " << numCapacite( i ) << endmsg;
      flot << nomCapacite( i ) << endmsg;
    }

  func() << "Race::afficheInfo -> fin" << endmsg;
  /*Debug::*/depileNiveau();
}

/**
 * Permet de fixer les capacités raciales une fois le personnage
 * créé. Est utilisé lors de la création d'une Race en lisant un
 * fichier XML (en mode console ou graphique).
 *
 * @param[in] capa conteneur des capacités.
 */
void Race::setCapacites(const vector<unsigned>& capa)
{
  Capacites.assign( capa.begin(), capa.end() );
}

/**
 * Gestion des niveau de sortie des fonctions.
 *
 * @param[in] level le niveau (INFO, DEBUG, ...).
 * @param[in] n permet de sélectionner quel variable est affectée.
 */
void Race::setLevel(const unsigned& level, const unsigned& n)
{
  switch(n)
    {
    case 1:
      elementaire = static_cast< DebugLevelType >( level );
      break;
    case 2:
      fonction = static_cast< DebugLevelType >( level );
      break;
    default:
      FlotMessage warn( WARN, "Race" );
      warn << "Variable inconnue, dans Race::setLevel " << n << endmsg;
    }
}

/**
 * Permet de savoir si on peut construire la race voulue avec les
 * traits donnés.
 *
 * @param[in,out] tab tableau des traits, attention, est modifé par
 * l'appel à appliquerAjustements.

 * @return StatusCode, \e true si possible, \e false sinon. Le champ
 * Valeur contient le numéro du trait qui empêche la création et
 * Message une indication.
 */
StatusCode Race::rempliPrerequis(vector< unsigned >& tab)
{
  if ( PrerequisRaciaux.size() == 0 )
    {
      appliquerAjustements( tab );
      return StatusCode( true );
    }
  else
    {
      vector< Prerequis >::const_iterator it, Begin( PrerequisRaciaux.begin() ),
	End( PrerequisRaciaux.end() );

      StatusCode sc;

      for (it = Begin; it != End; it++)
	{
	  sc = it -> estOK( tab );
	  if ( ! sc )
	    return sc;
	}
      appliquerAjustements( tab );
      return sc;
    }
}

/**
 * Méthode qui applique les ajustements sur les traits.
 *
 * On appelle ensuite la méthode bonusTrait, qui permet d'appliquer un
 * bonus supplémentaire pour certaines races, suivant les capacités
 * raciales sélectionnées.
 *
 * @param[in,out] tab tableau contenant les 6 traits.
 */
void Race::appliquerAjustements(vector< unsigned >& tab)
{
  map< unsigned, int >::const_iterator ajIt,
    ajEnd( AjustementsRaciaux.end() );

  for (ajIt = AjustementsRaciaux.begin(); ajIt != ajEnd; ajIt++)
    tab[ ajIt -> first ] += ajIt -> second;

  if ( BonusAuTrait != 0 )
    {
      if ( find( Capacites.begin(), Capacites.end(),
		 BonusAuTrait -> CapaNumero ) != Capacites.end() )
	tab[ BonusAuTrait -> TraitNumero ] += BonusAuTrait -> Ajustement;
    }
}
